
#include "los_config.h"
#ifdef LOSCFG_FS_PROC
#include "string.h"
#include "errno.h"
#include "stdio.h"
#include "stdlib.h"
#include "proc_fs.h"
#include "limits.h"
#include "linux/seq_file.h"


static int reallocate_larger_buf(struct seq_file *seqfilep, size_t oldcount)
{
    char * largerbuf = (char *)NULL;

    /* check error! */
    if ((NULL == seqfilep) || (NULL == seqfilep->buf))
    {
        return -1;
    }

    /* limitted the size of the seq file, small than 1M. */
    if (seqfilep->size >= 256 * PAGE_SIZE)
    {
        /* error! */
        free(seqfilep->buf);
        seqfilep->buf = (char *)NULL;
        return -1;
    }

    largerbuf = (char *)malloc(seqfilep->size <<= 1);
    if (NULL == largerbuf)
    {
        /* error! */
        free(seqfilep->buf);
        seqfilep->buf = (char *)NULL;
        return -1;
    }
    (void)memset_s(largerbuf, seqfilep->size, 0, seqfilep->size);

    /* get the data before the latest vsprintf. */
    (void)memcpy_s(largerbuf, seqfilep->size, seqfilep->buf, oldcount);

    seqfilep->count = oldcount;

    /* free the old small buf. */
    free(seqfilep->buf);
    /* point to the new larger buf. */
    seqfilep->buf = largerbuf;

    return 0;
}


int seq_open(struct proc_file * procfilep, const struct seq_operations *op)
{
    struct seq_file *seqfilep = (struct seq_file *)NULL;

    if ((NULL == procfilep) || (NULL == op) || (NULL == op->show))
    {
        return -1;
    }

    if (NULL != procfilep->private_data)
    {
        return -1;
    }

    seqfilep = (struct seq_file *)malloc(sizeof(struct seq_file));
    if (NULL == seqfilep)
    {
        return -1;
    }
    (void)memset_s(seqfilep, sizeof(struct seq_file), 0, sizeof(struct seq_file));

    seqfilep->op = op;
    procfilep->private_data = seqfilep;

    return 0;
}


ssize_t seq_read(struct proc_file *procfilep, char *buf, size_t readsize, loff_t *ppos)
{
    struct seq_file *seqfilep = (struct seq_file *)NULL;
    size_t realcopysize = 0; //real copy size.
    int    ret;

    /* check error! */
    if ((NULL == procfilep) || (NULL == procfilep->private_data) || (NULL == buf) || (NULL == ppos) || (*ppos < 0))
    {
        goto READ_ERROR;
    }

    seqfilep = (struct seq_file *)procfilep->private_data;

    /* make sure only show once. */
    if ((NULL != seqfilep->op->show) && (NULL == seqfilep->buf))
    {
        /* call the show function to build the seq file. */
        if (0 != seqfilep->op->show(seqfilep, NULL))
        {
            goto READ_ERROR;
        }
    }

    /* if the count is 0, means never call the seq_printf in the show function, no need to malloc the buf. */
    if ((*ppos >= seqfilep->count) || (0 == readsize))
    {
        /* there's no data at or after the file tail. */
        realcopysize = 0;
    }
    else
    {
        /* because of the return type is signed int, so the uwRealCopySize cann't bigger than INT_MAX. */
        realcopysize = min((seqfilep->count - *ppos), min(readsize, INT_MAX));
        ret = memcpy_s(buf, realcopysize, seqfilep->buf + *ppos, realcopysize);
        if (ret != 0)
        {
            goto READ_ERROR;
        }

        seqfilep->read_pos = *ppos + realcopysize;
        procfilep->f_pos = seqfilep->read_pos;
    }
    goto READ_FINISH;

READ_ERROR:
    return -1;

READ_FINISH:
    return (ssize_t)realcopysize;
}


loff_t seq_lseek(struct proc_file *procfilep, loff_t offset, int origin)
{
    struct seq_file *seqfilep = (struct seq_file *)NULL;
    loff_t absoluteoffset = 0; //absolute offset.

    /* check error. */
    if ((NULL == procfilep) || (NULL == procfilep->private_data))
    {
        return -1;
    }

    seqfilep = (struct seq_file *)procfilep->private_data;
    switch (origin)
    {
        case SEEK_CUR:
            absoluteoffset = offset + seqfilep->read_pos;
            break;

        case SEEK_SET:
            absoluteoffset = offset;
            break;

        default :
            absoluteoffset = -1;
            break;
    }

    if (absoluteoffset >= 0)
    {
        seqfilep->read_pos = absoluteoffset;
        procfilep->f_pos = seqfilep->read_pos;

        return absoluteoffset;
    }

    return -EINVAL;
}


int seq_release(struct inode *inodep, struct proc_file *procfilep)
{
    struct seq_file *seqfilep = (struct seq_file *)NULL;

    if ((NULL == procfilep) || (NULL == procfilep->private_data))
    {
        return -1;
    }

    seqfilep = (struct seq_file *)procfilep->private_data;

    /* if never call the seq_printf in the show function, the buf is NULL. */
    if (NULL != seqfilep->buf)
    {
        free(seqfilep->buf);
        seqfilep->buf = NULL;
    }
    free(seqfilep);

    procfilep->private_data = NULL;

    return 0;
}


int seq_printf(struct seq_file *seqfilep, const char *f, ...)
{
    int buflen = 0;
    size_t oldcount = 0;
    va_list arglist;
    bool needreprintf = FALSE; //if need re-printf.

    /* check error! */
    if (NULL == seqfilep)
    {
        return -1;
    }

    /* memory alloc the buf. */
    if (NULL == seqfilep->buf)
    {
        seqfilep->buf = (char *)malloc(seqfilep->size = PAGE_SIZE);
        if (NULL == seqfilep->buf)
        {
            /* no memory, error! */
            return -1;
        }
        (void)memset_s(seqfilep->buf, seqfilep->size, 0, seqfilep->size);
        seqfilep->count = 0;
    }

    do {
        oldcount = seqfilep->count;

        /* write the new data. */
        va_start(arglist, f);
        buflen = vsnprintf(seqfilep->buf + seqfilep->count, seqfilep->size - seqfilep->count, f, arglist);
        va_end(arglist);
        if (buflen < 0)
        {
            free(seqfilep->buf);
            seqfilep->buf = (char *)NULL;
            return -1;
        }

        if (seqfilep->count + buflen < seqfilep->size)
        {
            /* succeed write. */
            seqfilep->count += buflen;
            return 0;
        }

        /* need re-printf the data after we get a larger buf. */
        needreprintf = TRUE;

        /* means the buf is too small, we need a larger one. */
        if (0 != reallocate_larger_buf(seqfilep, oldcount))
        {
            /* error! */
            return -1;
        }
    } while(needreprintf);

    return -1;
}


int single_open(struct proc_file *procfilep, int (*show)(struct seq_file *, void *),
        void *data)
{
    struct seq_operations *seqoperationp = (struct seq_operations *)NULL;
    struct seq_file *seqfilep = (struct seq_file *)NULL;

    if ((NULL == procfilep) || (NULL == show) || (NULL != procfilep->private_data))
    {
        return -1;
    }

    seqoperationp = (struct seq_operations *)malloc(sizeof(struct seq_operations));
    if (NULL == seqoperationp)
    {
        return -1;
    }
    (void)memset_s(seqoperationp, sizeof(struct seq_operations), 0, sizeof(struct seq_operations));

    seqoperationp->start = NULL;
    seqoperationp->stop = NULL;
    seqoperationp->next = NULL;
    seqoperationp->show = show;

    if (0 != seq_open(procfilep, seqoperationp))
    {
        free((void *)seqoperationp);
        return -1;
    }

    seqfilep = (struct seq_file *)procfilep->private_data;
    seqfilep->private = data;

    return 0; /*lint !e429*/
}

int single_release(struct inode *inodep, struct proc_file *procfilep)
{
    const struct seq_operations *seqoperationp = (const struct seq_operations *)NULL;
    int reval = 0;

    if ((NULL == procfilep) || (NULL == procfilep->private_data))
    {
        return -1;
    }

    seqoperationp = ((struct seq_file *)procfilep->private_data)->op;
    reval = seq_release(inodep, procfilep);

    free((void *)seqoperationp);

    return reval;
}
#endif
